<?php
class Context {
	
	private static $instance;
	
	private $objectArray = array();
	private $resourceArray = array();
	
	private $resourceMapping;
	
	private $root;
	
	private $logger;
	
	private function __construct($configFile) {
	
		$this->logger = LoggerFactory::getLogger('Context');
		
		$this->resourceMapping = new URIMapping(URIMapping::$root);
	}
	
	public static function init($configFile) {
	
		if (!isset(self::$instance)) {
			self::$instance = new Context($configFile);
		}
		
		self::$instance->logger->debug('Loading XML configuration file...');
		
		if ($configFile == NULL) {
			throw new BadMethodCallException('Missing configFile parameter in Context');
		}
		
		$doc = new DOMDocument();
		$success = $doc->load($configFile);
		if (!$success) {
			throw new BadMethodCallException('Cannot load context file ' . $configFile);
		}
		
		self::$instance->loadConfig($doc);
		self::$instance->loadObjects($doc);
		self::$instance->loadControllers($doc);
		self::$instance->loadViews($doc);
	}
	
	private static function getInstance() {
		
		if (!isset(self::$instance)) {
			throw new RuntimeException('Context has not been initialized yet.');
		}
		
		return self::$instance;
	}
	
	private function loadConfig(DOMDocument $doc) {
	
		$this->logger->debug('Loading configuration...');
	
		$configRoot = $doc->getElementsByTagName('configuration');
	
		if ($configRoot->length == 0) {
			$this->logger->debug('No configuration found');
			return;
		}
	
		$configElementList = $configRoot->item(0)->childNodes;
		foreach($configElementList as $configElement) {
				
			if (strcmp($configElement->nodeName, 'root') == 0) {
				$this->root = $configElement->nodeValue;
				$this->logger->debug('Root set to ' . $this->root);
			}
		}
	}
	
	private function createObject(DOMNode $node) {
		
		$attributes = $node->attributes;
		$objectId = $attributes->getNamedItem('id')->nodeValue;
		$objectClass = $attributes->getNamedItem('class')->nodeValue;
			
		$class = new ReflectionClass($objectClass);
			
		$argumentArray = array();
		$childList = $node->childNodes;
		foreach($childList as $child) {
		
			if (strcmp($child->nodeName, 'constructorArg') == 0) {
				$argumentArray[] = $child->nodeValue;
			}
		}
		
		$objectInstance = $class->newInstanceArgs($argumentArray);
		
		return $objectInstance;
	}
	
	private function addResourceReference(Resource $resource, $uri, $resourceId) {
		
		$resourceReference = new ResourceReference($resource, $uri);
		$this->resourceArray[$resourceId] = $resourceReference;
	}
	
	private function loadObjects(DOMDocument $doc) {
		
		$this->logger->debug('Loading objects...');
		
		$objectElementList = $doc->getElementsByTagName('object');
		foreach ($objectElementList as $key => $objectElement) {
			
			$attributes = $objectElement->attributes;
			$objectId = $attributes->getNamedItem('id')->nodeValue;
			
			if ($this->objectArray[$objectId] != NULL) {
				throw new RuntimeException('Multiple declarations for object ID ' . $objectId);
			}
			
			$objectInstance = $this->createObject($objectElement);
			
			$this->objectArray[$objectId] = $objectInstance;
		}
	}
	
	private function loadControllers(DOMDocument $doc) {
		
		$this->logger->debug('Loading controllers...');
		
		$controllerElementList = $doc->getElementsByTagName('controller');
		foreach ($controllerElementList as $key => $controllerElement) {
			
			$controller = $this->createObject($controllerElement);
			$attributes = $controllerElement->attributes;
			$controllerId = $attributes->getNamedItem('id')->nodeValue;
			$controllerURI = $attributes->getNamedItem('path')->nodeValue;
			
			if ($this->resourceArray[$controllerId] != NULL) {
				throw new RuntimeException('Multiple declarations for resource ID ' . $controllerId);
			}
			
			$this->addResourceReference($controller, $controllerURI, $controllerId);
			$this->resourceMapping->addResource($controllerURI, $controller);
		}
	}
	
	private function loadViews(DOMDocument $doc) {
	
		$this->logger->debug('Loading views...');
	
		$viewElementList = $doc->getElementsByTagName('view');
		foreach ($viewElementList as $key => $viewElement) {
				
			$attributes = $viewElement->attributes;
			$viewId = $attributes->getNamedItem('id')->nodeValue;
			$viewURI = $attributes->getNamedItem('path')->nodeValue;
			$viewFile = $attributes->getNamedItem('file')->nodeValue;
			
			if ($this->resourceArray[$viewId] != NULL) {
				throw new RuntimeException('Multiple declarations for resource ID ' . $viewId);
			}
				
			$view = new View($viewFile);
			$this->addResourceReference($view, $viewURI, $viewId);
			$this->resourceMapping->addResource($viewURI, $view);
		}
	}
	
	public static function getObject($objectId) {
		
		if ($objectId == NULL) {
			throw new BadMethodCallException('Missing objectId parameter in Context->getObject');
		}
		
		$context = Context::getInstance();
		
		return $context->objectArray[$objectId];
	}
	
	public static function getResource($resourceId) {
		
		if ($resourceId == NULL) {
			throw new BadMethodCallException('Missing objectId parameter in Context->getController');
		}
		
		$context = Context::getInstance();
		
		return $context->resourceArray[$resourceId]->getResource();
	}
	
	public static function getResourceURI($resourceId) {
	
		if ($resourceId == NULL) {
			throw new BadMethodCallException('Missing objectId parameter in Context->getController');
		}
		
		$context = Context::getInstance();
	
		return $context->resourceArray[$resourceId]->getURI();
	}
	
	public static function resourceFromURI($uri) {
		
		if ($uri == NULL) {
			throw new BadMethodCallException('Missing objectId parameter in Context->resourceFromURI');
		}
		
		$context = Context::getInstance();
		
		$uri = substr($uri, strlen($context->root));
		$context->logger->debug('Resource at URI ' . $uri . ' requested.');
		return $context->resourceMapping->getResource($uri);
	}
}
?>